<?php

class BPNN
{
    private $input_num;
    private $hidden_num;
    private $output_num;
    private $input_data = null;
    private $output_data = null;
    private $weight_ih;
    private $weight_ho;
    private $learnrate;
    private $hi, $ho, $oi, $oo;
    private $num = 0;
    public function __construct($input_num, $output_num, $hidden_num, $learnrate)
    {

        $this->input_num = $input_num;
        $this->hidden_num = $hidden_num;
        $this->output_num = $output_num;
        $this->learnrate = $learnrate;
        $this->init();

    }
    public function get_input_data()
    {
        return $this->input_data;
    }
    public function set_input_data($data)
    {
        $this->input_data = $data;
    }
    public function get_output_data()
    {
        return $this->output_data;
    }
    public function set_output_data($data)
    {
        $this->output_data = $data;
    }

    private function clrio()
    {
        for ($i = 0; $i <= $this->hidden_num; $i++) {
            $this->hi[$i] = 0;
            $this->ho[$i] = 0;
        }
        for ($i = 0; $i < $this->output_num; $i++) {
            $this->oi[$i] = 0;
            $this->oo[$i] = 0;
        }
    }
    private function init()
    {
        $this->clrio();

        for ($i = 0; $i < $this->input_num; $i++) {
            for ($j = 0; $j < $this->hidden_num; $j++) {
                $this->weight_ih[$i][$j] = rand(0, 10000) / 10000.0;

            }
        }
        for ($j = 0; $j < $this->hidden_num; $j++) {
            $this->weight_ih[$this->input_num][$j] = 0;
        }

        for ($i = 0; $i < $this->hidden_num; $i++) {
            for ($j = 0; $j < $this->output_num; $j++) {
                $this->weight_ho[$i][$j] = rand(0, 10000) / 10000.0;
            }
        }
        for ($j = 0; $j < $this->output_num; $j++) {
            $this->weight_ho[$this->hidden_num][$j] = 0;
        }

    }
    private function _train($err,$show=0)
    {
        if (is_null($this->input_data) || is_null($this->output_data)) {
            echo "No Input Data or Output Data!!!\n";
            exit;

        }
        $err_ = $this->fp();
        $this->num = 1;
        while ($err_ > $err) {
	    if($show)
           	 printf("train %d %s and err = %f\n", $this->num, $this->num == 1 ? "time" : "times", $err_);
            $this->bp();
            $err_ = $this->fp();
            $this->num++;

        }
    }
    private function fp($err = 0)
    {
        $this->clrio();
        $_err = 0;
        for ($i = 0; $i < $this->hidden_num; $i++) {
            for ($j = 0; $j < $this->input_num; $j++) {
                $this->hi[$i] += $this->input_data[$j] * $this->weight_ih[$j][$i];
            }
            $this->hi[$i] += $this->weight_ih[$this->input_num][$i];
        }
        for ($i = 0; $i < $this->hidden_num; $i++) {
            $this->ho[$i] = $this->sigmoid($this->hi[$i]);
        }
        for ($i = 0; $i < $this->output_num; $i++) {
            for ($j = 0; $j < $this->hidden_num; $j++) {
                $this->oi[$i] += $this->ho[$j] * $this->weight_ho[$j][$i];
            }
            $this->oi[$i] += $this->weight_ho[$this->hidden_num][$i];
        }
        for ($i = 0; $i < $this->output_num; $i++) {
            $this->oo[$i] = $this->sigmoid($this->oi[$i]);
            $_err +=abs ($this->output_data[$i] - $this->oo[$i]);
        }
        $_err/=$this->output_num;
        if (!$err) {
            return $_err;

        } else {
            return $this->oo;
        }

    }
    private function bp()
    {
        for ($i = 0; $i <= $this->hidden_num; $i++) {
            $hidden_s[$i] = 0;
        }
        for ($i = 0; $i <= $this->hidden_num; $i++) {
            for ($j = 0; $j < $this->output_num; $j++) {
                $temp = ($this->oo[$j] - $this->output_data[$j]) * $this->sigmoid($this->oo[$j], 1);
                $hidden_s[$i] += $temp * $this->weight_ho[$i][$j];
                $this->weight_ho[$i][$j] -= $this->learnrate * $temp * $this->ho[$i];
            }

        }
        for ($i = 0; $i < $this->input_num; $i++) {
            for ($j = 0; $j < $this->hidden_num; $j++) {
                $this->weight_ih[$i][$j] -= $this->learnrate * $hidden_s[$j] * $this->sigmoid($this->ho[$j], 1) * $this->input_data[$i];
            }
        }
        for ($i = 0; $i < $this->hidden_num; $i++) {
            $this->weight_ih[$this->input_num][$i] -= $this->learnrate * $hidden_s[$i] * $this->sigmoid($this->ho[$i], 1);
        }

    }

    private function sigmoid($value, $dir = 0)
    {
        if (!$dir) {
            return 1.0 / (1.0 + exp(-1.0 * $value));
        } else {
            return $value * (1.0 - $value);
        }
    }
    public function echoweight()
    {
        echo "weight_ih\n";
        print_r($this->weight_ih);
        echo "\nweight_ho\n";
        print_r($this->weight_ho);
    }
    public function test($input)
    {
        $this->set_input_data($input);
        return $this->fp(1);
    }
    public function train($input, $output, $err,$show=0)
    {
        $this->set_input_data($input);
        $this->set_output_data($output);
        $this->_train($err,$show);
    }
    public function err($input, $output)
    {
        $this->set_input_data($input);
        $this->set_output_data($output);
        return $this->fp();
    }


}
$bpnn = new BPNN(2, 1, 5,0.2);

while(true) {
    $bpnn->train(array(1, 2), array(0.7), 0.001, 1);
    $bpnn->train(array(100, 45), array(0.2), 0.001, 1);
    $err=$bpnn->err(array(1, 2), array(0.7));
    $err+=$bpnn->err(array(100, 45), array(0.2));
    if($err<0.01)break;
}


echo $bpnn->test(array(1,4))[0]."\n";

